﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.ExceptionServices;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using CatClient.Configuration;
using CatClient.Context;
using CatClient.Fusing;
using CatClient.Message;
using CatClient.Message.Internals;
using CatClient.Message.Spi;
using CatClient.Message.Spi.Internals;
using CatClient.Util;

namespace CatClient
{
    public class Cat
    {
        private static readonly Cat _instance = new Cat();

        private static readonly object _lock = new object();

        public static string Version
        {
            get
            {
                return Assembly.GetExecutingAssembly().GetName().Version.ToString();
            }
        }

        public bool Initialized { get; private set; }

        public IMessageManager MessageManager { get; private set; }

        public IMessageProducer MessageProducer { get; private set; }

        internal FuseManager FuseManager { get; set; }

        public static string Success
        {
            get
            {
                return PureCatConstants.SUCCESS;
            }
        }

        public static void Initialize()
        {
            if (Cat._instance.Initialized)
                return;

            lock (_lock)
            {
                if (Cat._instance.Initialized)
                    return;
                
                LogInfoWriter.GetInstance("Cat", 0, LogInfoWriter.LogLayout.Default).Info("Initializing Cat Client.Cat.Version :" + Cat.Version);
                DefaultMessageManager defaultMessageManager = new DefaultMessageManager();
                defaultMessageManager.InitializeClient(CatConfig.Config);
                Cat._instance.MessageManager = defaultMessageManager;
                Cat._instance.MessageProducer = new DefaultMessageProducer(defaultMessageManager);
                Cat._instance.FuseManager = new FuseManager();
                Cat._instance.FuseManager.AddStatistics(new ConFuseStatistics());
                Cat._instance.FuseManager.AddStatistics(new ExFuseStatistics());
                Cat._instance.FuseManager.Start();
                Cat._instance.Initialized = true;
            }
            LogInfoWriter.GetInstance("Cat", 0, LogInfoWriter.LogLayout.Default).Info("Cat Client initialized.");
        }

        public static IMessageManager GetManager()
        {
            return Cat._instance.MessageManager;
        }

        public static IMessageProducer GetProducer()
        {
            return Cat._instance.MessageProducer;
        }

        internal static FuseManager GetFuseManager()
        {
            return Cat._instance.FuseManager;
        }

        public static bool IsInitialized()
        {
            if (!CatConfig.Config.Domain.Enabled)
            {
                return false;
            }
            bool initialized = Cat._instance.Initialized;
            if (initialized && !Cat._instance.MessageManager.HasContext())
            {
                Cat._instance.MessageManager.Setup();
            }
            return initialized;
        }

        public static bool IsInitializedWithoutSetup()
        {
            return CatConfig.Config.Domain.Enabled && Cat._instance.Initialized;
        }

        internal static bool JustInitialized()
        {
            return Cat._instance.Initialized;
        }

        public static void SetMainContextToCurrent(bool force)
        {
            Cat.GetManager().SetMainContextToCurrent(force);
        }

        public static T DoTransaction<T>(string type, string name, Func<T> func, Func<Exception, T> customCatch = null)
        {
            return Cat.DoTransaction<Exception, T>(type, name, func, customCatch, null);
        }

        public static T DoTransaction<T>(string type, string name, Func<T> func, Func<Exception, T> customCatch, Action finallyAction)
        {
            return Cat.DoTransaction<Exception, T>(type, name, func, customCatch, finallyAction);
        }

        public static T DoSqlTransaction<TE, T>(string name, string connstr, string cmd, Func<T> func, Func<TE, T> customCatch = null) where TE : Exception
        {
            return Cat.DoTransaction<TE, T>("SQL", name, func, customCatch, null);
        }

        public static T DoSqlTransaction<TE, T>(string name, string connstr, string cmd, Func<T> func, Func<TE, T> customCatch, Action finallyAction) where TE : Exception
        {
            return Cat.DoTransaction<TE, T>("SQL", name, func, customCatch, finallyAction);
        }

        public static void DoSqlTransaction<TE, T>(string name, Action action, Action<TE> customCatch = null) where TE : Exception
        {
            Cat.DoTransaction<TE>("SQL", name, action, customCatch);
        }

        public static T DoTransaction<TE, T>(string type, string name, Func<T> func, Func<TE, T> customCatch, Action finallyAction) where TE : Exception
        {
            ITransaction transaction = null;
            try
            {
                transaction = Cat.NewTransaction(type, name);
            }
            catch (Exception exception)
            {
                LogInfoWriter.GetInstance("Cat", 0, LogInfoWriter.LogLayout.Default).Error(string.Format("{0} Error", "DoTransaction"), exception.ToCatException());
            }

            T result;
            try
            {
                result = func();
            }
            catch (TE te)
            {
                TE expcption = (TE)((object)te);
                if (transaction != null)
                {
                    Cat.LogError(expcption);
                    transaction.SetStatus(expcption);
                }
                if (customCatch == null)
                {
                    throw;
                }
                result = customCatch(expcption);
            }
            finally
            {
                if (transaction != null)
                {
                    transaction.Complete();
                }
                if (finallyAction != null)
                {
                    finallyAction();
                }
            }
            return result;
        }

        public static void DoTransaction(string type, string name, Action action, Action<Exception> customCatch = null)
        {
            Cat.DoTransaction<Exception>(type, name, action, customCatch);
        }

        public static void DoTransaction<TE>(string type, string name, Action action, Action<TE> customCatch = null) where TE : Exception
        {
            ITransaction transaction = null;
            try
            {
                transaction = Cat.NewTransaction(type, name);
            }
            catch (Exception exception)
            {
                LogInfoWriter.GetInstance("Cat", 0, LogInfoWriter.LogLayout.Default).Error(string.Format("{0} Error", "DoTransaction"), exception.ToCatException());
            }
            try
            {
                action();
            }
            catch (TE te)
            {
                TE exp = (TE)((object)te);
                if (transaction != null)
                {
                    Cat.LogError(exp);
                    transaction.SetStatus(exp);
                }
                if (customCatch == null)
                {
                    throw;
                }
                customCatch(exp);
            }
            finally
            {
                if (transaction != null)
                {
                    transaction.Complete();
                }
            }
        }

        public static T DoEntryTransaction<T>(IEntryTranProcesser processer, Func<T> func)
        {
            return Cat.DoEntryTransaction<T>(processer, func, null, null);
        }

        public static T DoEntryTransaction<T>(IEntryTranProcesser processer, Func<T> func, Func<Exception, T> exceptionHandler, Action finallyAction)
        {
            return Cat.DoEntryTransaction<T>(processer, false, func, exceptionHandler, finallyAction);
        }

        public static T DoEntryTransaction<T>(IEntryTranProcesser processer, bool ignoreCore, Func<T> func, Func<Exception, T> exceptionHandler, Action finallyAction)
        {
            List<ITransaction> list = new List<ITransaction>();
            try
            {
                list = Cat.NewEntryTransaction(processer, ignoreCore);
            }
            catch (Exception exception)
            {
                LogInfoWriter.GetInstance("Cat", 0, LogInfoWriter.LogLayout.Default).Error(string.Format("{0} Error", "DoEntryTransaction"), exception.ToCatException());
            }
            T result;
            try
            {
                result = func();
            }
            catch (Exception ex)
            {
                if (list.Count > 0)
                {
                    list[list.Count - 1].SetStatus(ex);
                    Cat.LogError(ex);
                }
                if (exceptionHandler == null)
                {
                    throw;
                }
                result = exceptionHandler(ex);
            }
            finally
            {
                for (int i = list.Count - 1; i >= 0; i--)
                {
                    list[i].Complete();
                }
                if (finallyAction != null)
                {
                    finallyAction();
                }
            }
            return result;
        }

        public static T DoRemoteCallClient<T>(string url, Func<CatContext, Action<CatResponseContext>, T> func, Func<Exception, T> exceptionHandler, Action finallyAction)
        {
            return Cat.DoRemoteCallClient<T>(url, null, func, exceptionHandler, finallyAction);
        }

        public static T DoRemoteCallClient<T>(string url, string protocol, Func<CatContext, Action<CatResponseContext>, T> func, Func<Exception, T> exceptionHandler, Action finallyAction)
        {
            return Cat.DoRemoteCallClient<T>(url, protocol, func, (Exception ex, ITransaction t) => exceptionHandler(ex), finallyAction);
        }

        public static T DoRemoteCallClient<T>(string url, Func<CatContext, Action<CatResponseContext>, T> func, Func<Exception, ITransaction, T> exceptionHandler, Action finallyAction)
        {
            return Cat.DoRemoteCallClient<T>(url, null, func, exceptionHandler, finallyAction);
        }

        public static T DoRemoteCallClient<T>(string url, string protocol, Func<CatContext, Action<CatResponseContext>, T> func, Func<Exception, ITransaction, T> exceptionHandler, Action finallyAction)
        {
            ITransaction tran = null;
            CatContext arg = new CatContext();
            try
            {
                if (!url.StartsWith("http://")  && !url.StartsWith("https://") && !Regex.IsMatch(url, "^[a-z0-9]+\\://", RegexOptions.IgnoreCase))
                {
                    url = "http://" + url;
                }
                Uri uri = new Uri(url);
                tran = Cat.NewTransaction("PigeonCall", uri.AbsolutePath);
                Cat.LogEvent("PigeonCall.protocol", protocol ?? uri.Scheme, "0", null);
                Cat.LogEvent("PigeonCall.server", uri.Host, Cat.Success, uri.Query);
                Cat.LogEvent("PigeonCall.port", uri.Port.ToString(), "0", null);
                arg = Cat.LogRemoteCallClient();
            }
            catch (UriFormatException exception)
            {
                LogInfoWriter.GetInstance("Cat", 0, LogInfoWriter.LogLayout.Default).Error(string.Format("{0} UriFormatException: ", "DoRemoteCallClient") + url, exception);
            }
            catch (Exception exception2)
            {
                LogInfoWriter.GetInstance("Cat", 0, LogInfoWriter.LogLayout.Default).Error(string.Format("{0} Error: ", "DoRemoteCallClient") + url, exception2.ToCatException());
            }
            T result;
            try
            {
                result = func(arg, delegate (CatResponseContext responseContext)
                {
                    if (tran != null)
                    {
                        if (responseContext == null)
                        {
                            Cat.LogEvent("PigeonCall.AppFormatError", url, "0", null);
                            return;
                        }
                        Cat.LogEvent("PigeonCall.app", responseContext.ServerDomain, "0", null);
                    }
                });
            }
            catch (Exception ex)
            {
                if (exceptionHandler == null)
                {
                    if (tran != null)
                    {
                        Cat.LogError(ex);
                        tran.SetStatus(ex);
                    }
                    throw;
                }
                result = exceptionHandler(ex, tran);
            }
            finally
            {
                ITransaction subTran = tran;
                if (subTran != null)
                {
                    subTran.Complete();
                }
                if (finallyAction != null)
                {
                    finallyAction();
                }
            }
            return result;
        }

        public static List<ITransaction> NewEntryTransaction(IEntryTranProcesser processer)
        {
            return Cat.NewEntryTransaction(processer, false);
        }

        public static List<ITransaction> NewEntryTransaction(IEntryTranProcesser processer, bool ignoreCore)
        {
            if (!Cat.IsInitialized())
            {
                return new List<ITransaction>();
            }
            List<ITransaction> list = new List<ITransaction>();
            string methodArgs = processer.GetMethodArgs();
            if (!Cat.HasMessage())
            {
                string clientIp = processer.GetClientIp();
                string requestArgs = processer.GetRequestArgs();
                CatContext catContext = processer.GetCatContext();
                if (catContext != null)
                {
                    string name = processer.GetName(EntryTranNameType.PigeonCall);
                    ITransaction transaction = Cat.NewTransaction("PigeonService", name);
                    IAdditionEntryTranProcesser additionEntryTranProcesser = processer as IAdditionEntryTranProcesser;
                    try
                    {
                        List<IEvent> tranItems = (additionEntryTranProcesser != null) ? additionEntryTranProcesser.EntryEvents() : null;
                        if (tranItems != null)
                        {
                            foreach (IEvent item in tranItems)
                            {
                                transaction.Children.Add(item);
                            }
                        }
                    }
                    catch (Exception exception)
                    {
                        LogInfoWriter.GetInstance("Cat", 0, LogInfoWriter.LogLayout.Default).Error("[NewEntryTransaction]获取自定义Event失败", exception);
                    }
                    Cat.LogEvent("PigeonService", name, Cat.Success, requestArgs);
                    Cat.LogEvent("PigeonService.client", clientIp, "0", null);
                    Cat.LogEvent("PigeonService.app", catContext.ContextName, "0", null);
                    Cat.LogRemoteCallServer(catContext);
                    list.Add(transaction);
                }
                else
                {
                    EntryTranNameType type = (HttpContext.Current == null) ? EntryTranNameType.NotHttpUrlCall : EntryTranNameType.UrlCall;
                    string name = processer.GetName(type);
                    ITransaction transaction = Cat.NewTransaction("URL", name);
                    IAdditionEntryTranProcesser additionEntryTranProcesser2 = processer as IAdditionEntryTranProcesser;
                    try
                    {
                        List<IEvent> events = (additionEntryTranProcesser2 != null) ? additionEntryTranProcesser2.EntryEvents() : null;
                        if (events != null)
                        {
                            foreach (IEvent evt in events)
                            {
                                transaction.Children.Add(evt);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        LogInfoWriter.GetInstance("Cat", 0, LogInfoWriter.LogLayout.Default).Error("[NewEntryTransaction]获取自定义Event失败", ex);
                    }
                    Cat.LogEvent("URL", name, Cat.Success, requestArgs);
                    list.Add(transaction);
                }
            }
            if (!ignoreCore)
            {
                string name = processer.GetName(EntryTranNameType.MethodCall);
                ITransaction lastTransaction = Cat.GetLastTransaction();
                if (!lastTransaction.Type.Equals("Core") || !lastTransaction.Name.Equals(name))
                {
                    ITransaction itemTran = Cat.NewTransaction("Core", name);
                    list.Add(itemTran);
                }
                Cat.LogEvent("Core", name, Cat.Success, methodArgs);
            }
            return list;
        }

        public static ITaggedTransaction NewTaggedTransaction(string type, string name, string tag)
        {
            if (!Cat.IsInitialized())
            {
                return NullMessage.TAGGEDTRANSACTION;
            }
            return Cat.GetProducer().NewTaggedTransaction(type, name, tag);
        }

        public static IForkedTransaction NewForkedTransaction(string type, string name)
        {
            if (!Cat.IsInitialized())
            {
                return NullMessage.FORKEDTRANSACTION;
            }
            return Cat.GetProducer().NewForkedTransaction(type, name);
        }

        public static IAsyncForkedTransaction NewAsyncTransaction(string type, string name)
        {
            if (!Cat.IsInitialized())
            {
                return NullMessage.ASYNCFORKEDTRANSACTION;
            }
            return Cat.GetProducer().NewAsyncForkedTransaction(type, name);
        }

        public static ITransaction NewTransaction(string type, string name)
        {
            if (!Cat.IsInitialized())
            {
                return NullMessage.TRANSACTION;
            }
            return Cat.GetProducer().NewTransaction(type, name);
        }

        public static IEvent NewEvent(string type, string name)
        {
            if (!Cat.IsInitialized())
            {
                return NullMessage.EVENT;
            }
            return Cat.GetProducer().NewEvent(type, name);
        }

        public static ITrace NewTrace(string type, string name)
        {
            if (!Cat.IsInitialized())
            {
                return NullMessage.TRACE;
            }
            return Cat.GetProducer().NewTrace(type, name);
        }

        public static string GetCurrentMessageId()
        {
            if (!Cat.IsInitialized())
            {
                return string.Empty;
            }
            IMessageTree threadLocalMessageTree = Cat.GetManager().ThreadLocalMessageTree;
            if (threadLocalMessageTree != null)
            {
                if (threadLocalMessageTree.MessageId == null)
                {
                    threadLocalMessageTree.MessageId = Cat.CreateMessageId();
                }
                return threadLocalMessageTree.MessageId;
            }
            return null;
        }

        public static string CreateMessageId()
        {
            if (!Cat.IsInitialized())
            {
                return string.Empty;
            }
            return Cat.GetProducer().CreateMessageId();
        }

        public static CatContext LogRemoteCallClient()
        {
            if (!Cat.IsInitialized())
            {
                return new CatContext();
            }
            CatContext catContext = new CatContext();
            IMessageTree threadLocalMessageTree = Cat.GetManager().ThreadLocalMessageTree;
            if (threadLocalMessageTree.MessageId == null)
            {
                threadLocalMessageTree.MessageId = Cat.CreateMessageId();
            }
            string messageId = threadLocalMessageTree.MessageId;
            string text = Cat.CreateMessageId();
            Cat.LogEvent("RemoteCall", catContext.ContextName, "0", text);
            string text2 = threadLocalMessageTree.RootMessageId;
            if (text2 == null)
            {
                text2 = threadLocalMessageTree.MessageId;
            }
            catContext.CatRootId = text2;
            catContext.CatParentId = messageId;
            catContext.CatChildId = text;
            return catContext;
        }

        public static void LogRemoteCallServer(CatContext ctx)
        {
            if (!Cat.IsInitialized())
            {
                return;
            }
            if (ctx == null)
            {
                return;
            }
            IMessageTree threadLocalMessageTree = Cat.GetManager().ThreadLocalMessageTree;
            string catChildId = ctx.CatChildId;
            string catRootId = ctx.CatRootId;
            string catParentId = ctx.CatParentId;
            if (catChildId != null)
            {
                threadLocalMessageTree.MessageId = catChildId;
            }
            if (catParentId != null)
            {
                threadLocalMessageTree.ParentMessageId = catParentId;
            }
            if (catRootId != null)
            {
                threadLocalMessageTree.RootMessageId = catRootId;
            }
        }

        public static void LogEvent(string type, string name, string status = "0", string nameValuePairs = null)
        {
            if (!Cat.IsInitialized())
            {
                return;
            }
            Cat.GetProducer().LogEvent(type, name, status, nameValuePairs);
        }

        public static void LogHeartbeat(string type, string name, string status = "0", string nameValuePairs = null)
        {
            if (!Cat.IsInitialized())
            {
                return;
            }
            Cat.GetProducer().LogHeartbeat(type, name, status, nameValuePairs);
        }

        public static void LogError(Exception ex)
        {
            if (!Cat.IsInitialized())
            {
                return;
            }
            Cat.GetProducer().LogError(ex);
        }

        public static void LogMetricForCount(string name, int count = 1)
        {
            Cat.LogMetricInternal(name, "C", count.ToString());
        }

        public static void LogMetricForDuration(string name, double value)
        {
            Cat.LogMetricInternal(name, "T", string.Format("{0:F}", value));
        }

        public static void LogMetricForSum(string name, double value)
        {
            Cat.LogMetricInternal(name, "S", string.Format("{0:F}", value));
        }

        public static void LogMetricForSum(string name, double value, int count = 1)
        {
            Cat.LogMetricInternal(name, "S,C", string.Format("{0},{1:F}", count, value));
        }

        private static void LogMetricInternal(string name, string status, string keyValuePairs = null)
        {
            if (!Cat.IsInitialized())
            {
                return;
            }
            Cat.GetProducer().LogMetric(name, status, keyValuePairs);
        }

        private static ITransaction GetLastTransaction(ITransaction tran)
        {
            IList<IMessage> children = tran.Children;
            if (children != null && children.Count > 0)
            {
                for (int i = tran.Children.Count - 1; i >= 0; i--)
                {
                    ITransaction transaction = tran.Children[i] as ITransaction;
                    if (transaction != null)
                    {
                        return Cat.GetLastTransaction(transaction);
                    }
                }
            }
            return tran;
        }

        public static ITransaction GetLastTransaction()
        {
            if (!Cat.IsInitialized())
            {
                return NullMessage.TRANSACTION;
            }
            ITransaction transaction = Cat.GetManager().ThreadLocalMessageTree.Message as ITransaction;
            if (transaction != null)
            {
                return Cat.GetLastTransaction(transaction);
            }
            return NullMessage.TRANSACTION;
        }

        public static bool HasMessage()
        {
            return Cat.IsInitialized() && Cat._instance.MessageManager.HasContext() && Cat._instance.MessageManager.ThreadLocalMessageTree.Message != null;
        }

        public static CatKeyContext CatKeyContext()
        {
            return new CatKeyContext();
        }

        internal static void ClearSenderQueue()
        {
            DefaultMessageManager defaultMessageManager = Cat._instance.MessageManager as DefaultMessageManager;
            if (defaultMessageManager == null)
            {
                return;
            }
            defaultMessageManager.Clear();
        }

        public static DefaultMessageManager.CurrentKey GetCurrent()
        {
            DefaultMessageManager defaultMessageManager = (DefaultMessageManager)Cat.GetManager();
            if (defaultMessageManager == null)
            {
                if (CatConfigManager.GetConfigObject<bool>("CurrentKeyCheck", true))
                {
                    LogInfoWriter.GetInstance("Cat", 0, LogInfoWriter.LogLayout.Default).Error(string.Format("{0} MessageManager is not {1}", "GetCurrent", "DefaultMessageManager"));
                }
                return null;
            }
            return defaultMessageManager.GetCurrent();
        }

        internal static void SetCurrent(DefaultMessageManager.CurrentKey key)
        {
            DefaultMessageManager defaultMessageManager = (DefaultMessageManager)Cat.GetManager();
            if (defaultMessageManager == null)
            {
                if (CatConfigManager.GetConfigObject<bool>("CurrentKeyCheck", true))
                {
                    LogInfoWriter.GetInstance("Cat", 0, LogInfoWriter.LogLayout.Default).Error(string.Format("{0} MessageManager is not {1}", "SetCurrent", "DefaultMessageManager"));
                }
                return;
            }
            defaultMessageManager.SetCurrent(key);
        }
    }
}
